/***
  This file is part of volchecker.

  Copyright 2011 Jens Schoedt

  volchecker is free software; you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as
  published by the Free Software Foundation; either version 2.1 of the
  License, or (at your option) any later version.

  volchecker is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with volchecker; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  USA.
***/


#include <unistd.h>             /* read, write, close */
#include <fcntl.h>              /* open */
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <pulse/pulseaudio.h>

#include <iostream>
using namespace std;

pa_context *context = NULL;
pa_threaded_mainloop* loop = NULL;
static pa_stream *stream = NULL;
unsigned channels = 0;

float threshold = 0.002;
int belowLimit = 1000;
int belowCounter = 0;
int state = 0; // 0=off 1=on

const char *on_command;
const char *off_command;
char on_control[3] = { 0xFF, 0x01, 0x01 };
char off_control[3] = { 0xFF, 0x01, 0x00 };

void show_error(const char *txt, bool show_pa_error = true) {
	if (show_pa_error)
		fprintf(stderr, "%s: %s \n", txt, pa_strerror(pa_context_errno(context)));
}

void triggerApp(bool on) {
	if (on) {
		printf("on command %s \n", on_command);
		execl(on_command, on_command, NULL);
		return;
	}
	printf("off command %s \n", on_command);
	execl(off_command, off_command, NULL);
}

void trigger(bool on) {
	int usbdev; /* handle to FTDI device */
	//char response[20]; /* receive buffer */
	printf("Trigger: %s \n", (on)?"on":"off");
	if (on_command && off_command) {
		triggerApp(on);
		return;
	}
	//1)I added a system call to my code using stty for 115200 Baud 8N1:
	system("stty -F /dev/ttyUSB0 9600");
	//2) Later in my code I opened the device for reading/writing:
	usbdev = open("/dev/ttyUSB0", O_RDWR);
	if (usbdev == -1) {
		fprintf(stderr, "Could not open USB device: /dev/ttyUSB0");
		return;
	}
	//3) I then used write to send, example uses 2 chars. These are defined prior to the call and can be any value from 0 to 255 for each char.:
	if (on)
		write(usbdev, on_control, 3);
	else
		write(usbdev, off_control, 3);
	//4) For receive, use the read command with the number of chars to receive, example uses 1:
	//read(usbdev, response, 20);
	//printf("Response - %s \n", response);
	//5) Add the following to close the device:
	close(usbdev);
}

static void check_level(float level) {
	if (level > threshold) {
		belowCounter = 0;
		if (state == 0) {
			// turn on
			trigger(true);
			state = 1;
		}
		return;
	}
	belowCounter++;
	if (state == 1 && belowCounter > belowLimit) {
		// turn off
		trigger(false);
		state = 0;
	}
}

static void stream_read_callback(pa_stream *s, size_t l, void *) {
	const void *d;
	if (pa_stream_peek(s, &d, &l) < 0) {
		show_error("pa_stream_peek() failed");
		return;
	}

	const float *data = (const float*) d;
	unsigned samples = l / sizeof(float);
	float level = 0;

	while (samples >= channels) {
		for (unsigned c = 0; c < channels; c++) {
			float v = fabs(data[c]);
			if (v > level)
				level = v;
		}
		data += channels;
		samples -= channels;
	}
	pa_stream_drop(s);

	//printf("Level : %f \n", level);
	check_level(level);
}

static void stream_state_callback(pa_stream *s, void *) {
	const pa_channel_map *pcm;

	switch (pa_stream_get_state(s)) {
	case PA_STREAM_UNCONNECTED:
	case PA_STREAM_CREATING:
		break;

	case PA_STREAM_READY:
		pcm = pa_stream_get_channel_map(s);
		channels = pcm->channels;
		break;

	case PA_STREAM_FAILED:
		show_error("Connection failed");
		break;

	case PA_STREAM_TERMINATED:
		show_error("Stream terminated");
		break;
	}
}

static void create_stream(const char *name, const char *description, const pa_sample_spec &ss, const pa_channel_map &cmap) {
	pa_sample_spec nss;

	nss.format = PA_SAMPLE_FLOAT32;
	nss.rate = 2;
	nss.channels = ss.channels;

	stream = pa_stream_new(context, "volchecker", &nss, &cmap);
	pa_stream_set_state_callback(stream, stream_state_callback, NULL);
	pa_stream_set_read_callback(stream, stream_read_callback, NULL);
	pa_stream_connect_record(stream, name, NULL, (enum pa_stream_flags) PA_STREAM_PEAK_DETECT);
}

static void context_get_source_info_callback(pa_context *, const pa_source_info *si, int is_last, void *) {
	if (is_last < 0) {
		show_error("Failed to get source information");
		return;
	}

	if (!si)
		return;

	create_stream(si->name, si->description, si->sample_spec, si->channel_map);
}

static void context_get_sink_info_callback(pa_context *, const pa_sink_info *si, int is_last, void *) {
	if (is_last < 0) {
		show_error("Failed to get sink information");
		return;
	}

	if (!si)
		return;

	create_stream(si->monitor_source_name, si->description, si->sample_spec, si->channel_map);
}

static void context_get_server_info_callback(pa_context *c, const pa_server_info*si, void *) {
	if (!si) {
		show_error("Failed to get server information");
		return;
	}

	// output
	if (!si->default_sink_name)
		show_error("No default sink set.", false);
	else
		pa_operation_unref(pa_context_get_sink_info_by_name(c, si->default_sink_name, context_get_sink_info_callback, NULL));

	// input
	if (!si->default_source_name)
		show_error("No default source set.", false);
	else
		pa_operation_unref(pa_context_get_source_info_by_name(c, si->default_source_name, context_get_source_info_callback, NULL));

}

static void context_state_callback(pa_context *c, void* userdata) {
	switch (pa_context_get_state(c)) {
	case PA_CONTEXT_UNCONNECTED:
	case PA_CONTEXT_CONNECTING:
	case PA_CONTEXT_AUTHORIZING:
	case PA_CONTEXT_SETTING_NAME:
		break;
	case PA_CONTEXT_READY:
		pa_operation_unref(pa_context_get_server_info(c, context_get_server_info_callback, NULL));
		break;
	case PA_CONTEXT_FAILED:
		show_error("Connection failed");
		break;
	case PA_CONTEXT_TERMINATED:
		show_error("Terminated");
		break;
	}
}

int main(int argc, char *argv[]) {
	const char* usage = "Checks the volume level of PulseAudio on both in and output. \n"
		"If the level gets above threshold on_script is executed. \n"
		"If the level is below threshold for some time off_script is executed. \n"
		"Threshold is between 0.000-1.000 default 0.002 \n"
		"Usage: volchecker on_script off_script threshold\n"
		"Example: volchecker /home/me/on.sh /home/me/off.sh 0.002\n";
	if (argc > 1 && argc < 4) {
		printf(usage);
		return -1;
	} else if (argc == 4) {
		on_command = argv[1];
		off_command = argv[2];
		threshold = atof(argv[3]);
	}
	printf(usage);
	printf("Using default configuration. You probably want:\n");

	trigger(false); // turn off to start with

	int pa_ready = 0;

	signal(SIGPIPE, SIG_IGN);

	loop = pa_threaded_mainloop_new();

	context = pa_context_new(pa_threaded_mainloop_get_api(loop), "volchecker");

	pa_context_connect(context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
	pa_context_set_state_callback(context, context_state_callback, &pa_ready);

	/* Run the main loop */
	if (pa_threaded_mainloop_start(loop) < 0) {
		show_error("Main loop failed");
	}

	while (true) {
		pa_threaded_mainloop_lock(loop);
		pa_threaded_mainloop_wait(loop);
		sleep(10);
	}

	if (context)
		pa_context_unref(context);

	pa_threaded_mainloop_free(loop);
	printf("Exiting");
	return 0;
}

